// TableView.cpp : implementation file
//

#include "stdafx.h"
#include "TableView.h"
#include "resource.h"

#include <algorithm>
#include <math.h>

static WNDPROC m_pWndProc = 0;

BEGIN_MESSAGE_MAP(CTableView,CWnd)
	ON_WM_PAINT()  
	ON_WM_VSCROLL()
	ON_WM_KEYDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEWHEEL()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_SIZE()
	ON_WM_KILLFOCUS()
END_MESSAGE_MAP()

CTableView::CTableView()
	:_caretInfo(NULL)
{
	_scrollPos = 0;
	_headerText.push_back(MAKEINTRESOURCE(IDS_ID));
	_headerText.push_back(MAKEINTRESOURCE(IDS_NAME));
	_headerText.push_back(MAKEINTRESOURCE(IDS_LINE));
	_headerText.push_back(MAKEINTRESOURCE(IDS_LATITUDE));
	_headerText.push_back(MAKEINTRESOURCE(IDS_LONGITUDE));
}

CTableView::~CTableView()
{
	delete _caretInfo;
}

extern "C" LRESULT FAR PASCAL TableViewProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	switch (nMsg) {

	case WM_RBUTTONDOWN: 
		break;
	}

	return CallWindowProc(m_pWndProc, hWnd, nMsg, wParam, lParam);
}

bool CTableView::CreateFromStatic(UINT nStaticID, CWnd* pParent)
{
	CStatic  wndStatic;		

	if (pParent == NULL || !wndStatic.SubclassDlgItem(nStaticID, pParent))
	{
		return false;
	}


	/* Get the static windows rectangle and transform it into
	parent client coordinates, instead of screen coordinates */

	CRect    rectStatic;

	wndStatic.GetWindowRect(&rectStatic);
	pParent->ScreenToClient(&rectStatic);


	if ( !CreateEx( wndStatic.GetExStyle(), 
		NULL, NULL, WS_CHILD | WS_VISIBLE | wndStatic.GetStyle(),
		rectStatic, pParent, nStaticID, NULL))
	{		
		wndStatic.DestroyWindow();
		return false;
	}

	return true;
}

void CTableView::OnPaint()
{
	CPaintDC paintDC(this);

	CDC &dc = InitBackBuffer(paintDC);

	//CRect &rectClient= getClientRect();
	dc.IntersectClipRect(&_clientRect);
	dc.FillSolidRect(&_clientRect,RGB(255,255,255));

	CRect rect(_clientRect);

	if (NeedScrollBar(rect))
	{
		rect.right -= SCROLLBARWIDTH;
		DrawScrollBar(dc);
	}

	DrawHeader(dc,rect);
	DrawContent(dc,rect);
	ReleaseBackBuffer(paintDC);
}

CDC& CTableView::InitBackBuffer(CPaintDC &dc)
{
	/* Create the offscreen DC and associated bitmap */

	_dcBack.CreateCompatibleDC(&dc);
	_bmBack.CreateCompatibleBitmap(&dc, _clientRect.Width(),
		_clientRect.Height());

	_pOldBitmap = _dcBack.SelectObject(&_bmBack);
	return _dcBack;
}

void CTableView::ReleaseBackBuffer(CPaintDC &dc)
{
	/* Copy the offscreen buffer to the onscreen CPaintDC.
	Then free the back buffer objects. */

	dc.BitBlt(_clientRect.left, _clientRect.top, 
		_clientRect.Width(),  _clientRect.Height(),
		&_dcBack, _clientRect.left, _clientRect.top,
		SRCCOPY);

	dc.SelectObject(_pOldBitmap);

	_bmBack.DeleteObject();
	_dcBack.DeleteDC();
}

void CTableView::DrawHeader( CDC &dc, const CRect &rect )
{
	CPen headerPen(PS_SOLID,1,RGB(148,171,233));
	CPen* pOldPen = dc.SelectObject(&headerPen);

	CRect headerRect(rect);
	headerRect.bottom = rect.top+CELL_HEIGHT;
	COLORREF color = dc.GetBkColor();
	dc.FillSolidRect(headerRect,RGB(148,171,233));
	DrawTextForStation(NULL,dc,headerRect,-1,false);
	dc.SetBkColor(color);
	dc.SelectObject(pOldPen);
	headerPen.DeleteObject();
}

void CTableView::DrawContent( CDC &dc, const CRect &rect )
{
	int nRows = _stations.size();
	CRect contentRect(rect);
	contentRect.bottom = contentRect.top + CELL_HEIGHT;


	CPen contentPen(PS_SOLID,1,RGB(148,171,233));
	CPen*pOldPen = dc.SelectObject(&contentPen);
	bool bSelected = false;
	for(int i = _scrollPos; i < nRows&& (i - _scrollPos) < floor((double)_clientRect.Height()/CELL_HEIGHT)-1; i++)
	{  
		auto itSelect = std::find(_selectedStations.begin(),_selectedStations.end(),_stations.at(i));
		bSelected = (itSelect != _selectedStations.end()) ? true:false;
		contentRect.top += CELL_HEIGHT;
		contentRect.bottom += CELL_HEIGHT;
		DrawTextForStation(_stations.at(i),dc,contentRect,i,bSelected);

	}

	dc.SelectObject(pOldPen);
	contentPen.DeleteObject();
}

void CTableView::AddStation(Station* pStation)
{
	_stations.push_back(pStation);
}

void CTableView::DrawTextForStation( const Station* pStation,CDC &dc, const CRect& contentRect, const int i,bool bSelected )
{
	CString strText;
	int cellWidth = contentRect.Width()/_headerText.size();
	CSize textSize;
	COLORREF color = dc.GetBkColor();
	if (bSelected)
		dc.FillSolidRect(&contentRect,RGB(54,156,218));
	for (unsigned int j = 0;j < _headerText.size(); ++j)
	{
		dc.MoveTo(contentRect.left+(j*cellWidth),contentRect.top);
		dc.LineTo(contentRect.left+(j*cellWidth),contentRect.bottom); 
		if (!pStation)
			strText = _headerText[j];
		else
			strText = pStation->Get(static_cast<StationObjects>(j));
		textSize = dc.GetTextExtent(strText);
		CPoint drawPoint((cellWidth - textSize.cx)/2+contentRect.left+(j*cellWidth),contentRect.top+(CELL_HEIGHT-textSize.cy)/2);
		if (_caretInfo && _caretInfo->IsDisplayed() && CellHasPoint(_caretInfo->Position(),i+1,j))
			_caretInfo->Update(&dc,ACTION_PAINT);
		else
			dc.TextOut(drawPoint.x,drawPoint.y,strText);
	}
	//draw right table line
	dc.MoveTo(contentRect.right-1,contentRect.top);
	dc.LineTo(contentRect.right-1,contentRect.bottom);
	//draw bottom line
	dc.MoveTo(contentRect.left,contentRect.bottom);
	dc.LineTo(contentRect.right,contentRect.bottom);

	dc.SetBkColor(color);
}

void CTableView::DrawScrollBar(CDC &dc)
{
	CPen scrollBarPen(PS_SOLID,1,RGB(210,210,210));

	CPen* oldPen = dc.SelectObject(&scrollBarPen);

	int screenItemsNo = static_cast<int>(floor((double)_clientRect.Height()/(CELL_HEIGHT) - 1));

	CRect scrollBarRect = _clientRect;
	scrollBarRect.left = scrollBarRect.right - SCROLLBARWIDTH;
	scrollBarRect.bottom = (screenItemsNo+1)*CELL_HEIGHT;

	COLORREF color = dc.GetBkColor();
	dc.FillSolidRect(scrollBarRect,RGB(210,210,210));

	_scrollRect = scrollBarRect;

	int nIemsToScroll = _stations.size() - screenItemsNo;

	int y = static_cast<int>(nIemsToScroll > 0 ?((double)scrollBarRect.bottom - SCROLLBARHEIGHT)/nIemsToScroll*_scrollPos: 0);
	_scrollRect.bottom = scrollBarRect.top+SCROLLBARHEIGHT;	
	_scrollRect.MoveToY(y);
	dc.Draw3dRect(_scrollRect,RGB(190,190,190),RGB(170,170,170));
	dc.SetBkColor(color);
	dc.SelectObject(oldPen);
	scrollBarPen.DeleteObject();
}

bool CTableView::NeedScrollBar(const CRect& rect)
{
	return (rect.Height()-CELL_HEIGHT < (int)_stations.size() * CELL_HEIGHT);
}


void CTableView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch(nChar)
	{
	case VK_UP:
		_scrollPos = std::max<int>(0,--_scrollPos);
		RedrawWindow();
		break;
	case VK_DOWN:
	case VK_NUMPAD2:
		_scrollPos = std::min<int>(_stations.size() - _clientRect.Height()/CELL_HEIGHT,++_scrollPos);
		RedrawWindow();
		break;
	}
	if (!(GetKeyState(VK_SHIFT) & (1 << (8*sizeof(short)-1))) && (GetKeyState(VK_CAPITAL) == 0))
	{
		if (isalpha(nChar))
			nChar = _tolower(nChar);
	}
	if (_caretInfo && _caretInfo->IsDisplayed())
	{
		_caretInfo->Update(&CPaintDC(this),nChar);
	}
}

void CTableView::OnMouseMove(UINT nFlags, CPoint point)
{
	SetFocus();
	if (_scrollRect.PtInRect(point) && NeedScrollBar(_clientRect) && (nFlags&MK_LBUTTON))
	{
		CRect oldSr = _scrollRect;
		int screenItemsNo = static_cast<int>(floor((double)_clientRect.Height()/(CELL_HEIGHT) - 1));
		int nIemsToScroll = _stations.size() - screenItemsNo;
		CRect scrollBarRect = _clientRect;
		scrollBarRect.left = scrollBarRect.right - SCROLLBARWIDTH;
		scrollBarRect.bottom = (screenItemsNo+1)*CELL_HEIGHT;
		if (scrollBarRect.top <= _scrollRect.top + (point.y - _mouseClickPos.y ) && 
			scrollBarRect.bottom >= _scrollRect.top + (point.y - _mouseClickPos.y )+_scrollRect.Height())
			_scrollRect.MoveToY(_scrollRect.top + (point.y - _mouseClickPos.y ));
		int olds = _scrollPos;
		_scrollPos = static_cast<int>(_scrollRect.top*nIemsToScroll/((double)scrollBarRect.bottom - SCROLLBARHEIGHT));
		if (olds != _scrollPos && _scrollPos <= nIemsToScroll && _scrollPos >= 0)
		{
			_mouseClickPos = point;
			RedrawWindow();
		}				
		else 
			_scrollPos = olds;
	}

	CWnd::OnMouseMove(nFlags,point);
}

void CTableView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	if (_caretInfo == NULL)
		_caretInfo = new CaretInfo(1,CELL_HEIGHT-2);
	_caretInfo->Create(this);
	UpdateCaretPos(point);
	CWnd::OnLButtonDblClk(nFlags,point);
}

void CTableView::OnKillFocus(CWnd* pNewWnd)
{
	_caretInfo->Destroy();
	if (_caretInfo->Action() == VK_RETURN)
		UpdateStation(_caretInfo->Position(),_caretInfo->Info());
	RedrawWindow();
	CWnd::OnKillFocus(pNewWnd);
}

BOOL CTableView::PreTranslateMessage(MSG* pMsg) 
{
	switch(pMsg->message)
	{
	case WM_KEYDOWN:
		switch(pMsg->wParam)
		{
		case VK_RETURN:
		case VK_LEFT:
		case VK_RIGHT:
		case VK_NUMPAD2:
		case VK_NUMPAD8:
		case VK_UP:
		case VK_DOWN:
			OnKeyDown(pMsg->wParam,1,1);
			return true;
			break;
		}
		break;
	default:
		return CWnd::PreTranslateMessage(pMsg);
	}


	return FALSE;
}

void CTableView::OnLButtonDown(UINT nFlags, CPoint point)
{
	_mouseClickPos = point;
	CWnd::OnLButtonDown(nFlags,point);
}

void CTableView::OnLButtonUp(UINT nFlags, CPoint point)
{
	_mouseClickPos.x = _mouseClickPos.y = 0;
	CWnd::OnLButtonUp(nFlags,point);
}

BOOL CTableView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	if (zDelta > 0)
		_scrollPos = std::max<int>(0,--_scrollPos);
	else if (zDelta < 0)
	{
		_scrollPos = std::min<int>(_stations.size() - RowNumber(),++_scrollPos);
	}

	RedrawWindow();
	return true;
}

void CTableView::OnSize(UINT nType, int cx, int cy)
{
	_scrollPos = 0;
	if (_caretInfo && _caretInfo->IsDisplayed())
	{
		int row, column;
		if (PointInCell(_caretInfo->Position(),&row,&column))
		{
			GetClientRect(&_clientRect);
			_caretInfo->SetCellWidth(CellWidth());
			_caretInfo->Position().x = _clientRect.left+column*CellWidth();
			_caretInfo->Position().y = _clientRect.top+row*CELL_HEIGHT + 1;
			_caretInfo->Update(&CPaintDC(this),ACTION_RESIZE);
			RedrawWindow();
		}

	}	
	else 
		GetClientRect(&_clientRect);
	CWnd::OnSize(nType,cx,cy);
}

void CTableView::Select( const Station* pStation, bool bAdd /*= false*/ )
{
	auto itStation = std::find(_stations.begin(),_stations.end(),pStation);
	if (*itStation)
	{
		if (!bAdd)
			_selectedStations.clear();     
		if (std::find(_selectedStations.begin(),_selectedStations.end(),*itStation) == _selectedStations.end())
			_selectedStations.push_back(*itStation);                 
	}
	RedrawWindow();
}

void CTableView::DeselectAll()
{
	_selectedStations.clear();
	RedrawWindow();
}

void CTableView::UpdateStation( const CPoint& point, const CString& strValue )
{
	int row,col;
	if (PointInCell(point,&row,&col))
	{
		auto station = _stations.begin();
		std::advance(station,row+_scrollPos-1);
		station[0]->Set(static_cast<StationObjects>(col),strValue);
	}
}

inline int CTableView::RowNumber()
{
	return static_cast<int>(floor((double)_clientRect.Height()/CELL_HEIGHT)-1);      
}

inline int CTableView::CellWidth(int cx)
{
	if (cx = -1)
		cx = _clientRect.Width();
	return (cx-SCROLLBARWIDTH)/_headerText.size();
}

bool CTableView::PointInCell( const CPoint& point,int* row, int* column )
{
	int r =point.y/CELL_HEIGHT ,c = point.x/CellWidth();
	if (row && column)
	{
		*row = r;
		*column = c;
	}
	return (r > 0 && r <= RowNumber() && c >= 0 && c < static_cast<int>(_headerText.size()));
}

BOOL CTableView::CellHasPoint( const CPoint& point, const int row, const int column )
{
	CRect cellRect;
	cellRect.left = CellWidth()*column;
	cellRect.top = CELL_HEIGHT*row;
	cellRect.right = cellRect.left + CellWidth();
	cellRect.bottom = cellRect.top + CELL_HEIGHT;

	return cellRect.PtInRect(point);
}

bool CTableView::UpdateCaretPos( const CPoint& point )
{
	int row,column;
	CPaintDC dc(this);
	if (PointInCell(point,&row,&column))
	{
		auto station = _stations.begin();
		std::advance(station,row+_scrollPos-1);
		CPoint caretPos;
		caretPos.x = _clientRect.left+column*CellWidth();
		caretPos.y = _clientRect.top+row*CELL_HEIGHT + (CELL_HEIGHT - dc.GetTextExtent(station[0]->Get(static_cast<StationObjects>(column))).cy)/2;

		_caretInfo->SetInfo(CellWidth(),&station[0]->Get(static_cast<StationObjects>(column)));
		_caretInfo->SetPosition(!_caretInfo->IsDisplayed(),&caretPos);
		RedrawWindow();
		return true;
	}
	return false;
}